package stud.mpgame.network;

import org.apache.mina.core.session.IoSession;
import stud.mpgame.network.service.ping.PingNotificationMessageDescription;
import stud.mpgame.network.service.ping.PingRequestMessageDescription;
import stud.mpgame.network.util.ResponseMessageDescription;

import java.util.HashSet;
import java.util.Set;

/**
 * Центральный класс сетевой библиотеки.
 *
 * Отвечает за установление соединения с клиентами, содержит в себе информацию о настройках сетевого соединения.
 * Также предоставляет возможность подписки класса на уведомления связанные с открытием/закрытием сессии.
 *
 * @author f.pavel91@gmail.com
 * @version 1.4
 */
public abstract class NetworkMonitor {

    private static final int DEFAULT_PORT = 8090;
    private static final int DEFAULT_MAX_MESSAGE_BUFFER_SIZE = 16384;

    private Set< ISessionListener > sessionListeners;
    protected long startAppTime;
    protected int protocolVersion;
    protected int port = DEFAULT_PORT;
    protected int maxMessageBufferSize = DEFAULT_MAX_MESSAGE_BUFFER_SIZE;

    private IMessageDispatcher messageDispatcher;
    private IMessageDispatcher responseMessageDispatcher;

    /**
     *
     * @param startAppTime время старта сервера.
     * @param protocolVersion версия протокола передачи данных.
     */
    public NetworkMonitor(long startAppTime, int protocolVersion) {
        this.sessionListeners = new HashSet< ISessionListener >();
        this.startAppTime = startAppTime;
        this.protocolVersion = protocolVersion;
        this.messageDispatcher = new MessageDispatcher();
        this.responseMessageDispatcher = new MessageDispatcher();

        //register necessary descriptions
        MessageDescription.registerDescription(ResponseMessageDescription.getInstance());
        MessageDescription.registerDescription(PingRequestMessageDescription.getInstance());
        MessageDescription.registerDescription(PingNotificationMessageDescription.getInstance());
    }

    /**
     *
     * @return Порт, прослушиваемый данным {@link NetworkMonitor}.
     */
    public int getPort() {
        return port;
    }

    /**
     *
     * @return Максимальный размер передаваемого сообщения в байтах.
     */
    public int getMaxMessageBufferSize() {
        return maxMessageBufferSize;
    }

    /**
     *
     * @return Время старта сервера.
     */
    public long getStartAppTime() {
        return startAppTime;
    }

    /**
     *
     * @return Версия протокола передачи данных.
     */
    public int getProtocolVersion() {
        return protocolVersion;
    }

    /**
     * Устанавливает порт, который должен прослушивать данный {@link NetworkMonitor}.
     *
     * @param port Порт
     */
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Установление ограничения на максимальный объем передаваемого сообщения.
     *
     * @param maxMessageBufferSize максимальный размер сообщения в байтах.
     */
    public void setMaxMessageBufferSize(int maxMessageBufferSize) {
        this.maxMessageBufferSize = maxMessageBufferSize;
    }

    /**
     *
     * @return диспетчер сообщений.
     */
    public IMessageDispatcher getMessageDispatcher() {
        return messageDispatcher;
    }

    public IMessageDispatcher getResponseMessageDispatcher() {
        return responseMessageDispatcher;
    }

    /**
     * Прицепляет данный {@link NetworkMonitor} к указанному порту.
     * @throws Exception todo
     */
    abstract public void bind() throws Exception;

    /**
     * Подписка класса на уведомление об открытии сессии.
     *
     * @param listener класс, реализующий интерфейс {@link ISessionListener}.
     */
    public void addSessionListener(ISessionListener listener) {
        sessionListeners.add(listener);
    }

    /**
     * Подписка класса на уведомление о закрытии сессии.
     *
     * @param listener класс, реализующий интерфейс {@link ISessionListener}.
     */
    public void removeSessionListener(ISessionListener listener) {
        sessionListeners.remove(listener);
    }

    void notifySessionOpened(IoSession ioSession) {
        for (ISessionListener listener : sessionListeners) {
            listener.sessionOpened(ioSession);
        }
    }

    void notifySessionClosed(IoSession ioSession) {
        for (ISessionListener listener : sessionListeners) {
            listener.sessionClosed(ioSession);
        }
    }

    public int nowTime() {
        return (int) (System.currentTimeMillis() - startAppTime);
    }

}